#include "ReadOut.h"
#include <sys/time.h>

/*
直接控制AXI/GPIO接口的寄存器，输出一个脉冲给PL端的CCD驱动模块
*/
void Trig_Readout( uint32_t *gpio_reg_ctrl ){
    clrbit(*gpio_reg_ctrl, BIT_READOUT_TRIG);
    usleep(10);
    
    setbit(*gpio_reg_ctrl, BIT_READOUT_TRIG);
    usleep(10);
    
    clrbit(*gpio_reg_ctrl, BIT_READOUT_TRIG);
}

/*
ccd_readout发出信号驱动CCD读出，并可以根据传递的opt参数决定是否保存图像数据，以及如何保存
update@2024-04-02: 尝试在CCD数据转移至DDR的过程中，同时从
*/
int ccd_readout(    uint32_t *gpio_reg_ctrl, 
                    uint32_t *gpio_reg_status, 
                    uint32_t *gpio_reg_pl_ps_ddr_cnt, 
                    uint32_t n_row,
                    uint32_t n_col,
                    uint32_t n_bytes,    // 2 for 16bits unsigned; 4 for 32bits float
                    uint32_t fast_read_opt,
                    char *filename ){

    uint32_t base_addr = 0x20000000;    // 512MByte 特殊地址值！！！！
    int fd = open("/dev/mem", O_RDWR | O_SYNC);

/*    printf("address of mem_ptr: %p\n", mem_ptr);*/

    if( fast_read_opt == 1 ){
    	
    	printf("\t******************************\n");
    	printf("\t>>> readout in faster mode <<<\n");
    	printf("\t******************************\n");
    
        uint32_t mem_size_in_Bytes = n_col*16*n_bytes;   // the actual number of Bytes of a row
        uint32_t *mem_ptr = mmap(NULL, n_row*n_col*16*n_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, base_addr);
        uint32_t *ptr = mem_ptr;

        char fname[1024];
        
        sprintf(fname, "%s", filename);
        FILE *fb = fopen(fname, "wba");
        
        struct timeval start;
		struct timeval end;
        
		gettimeofday(&start, NULL);
        Trig_Readout( gpio_reg_ctrl );      /* trigger readout */

        uint32_t cnt = 0;
        uint32_t row_cnt        = 0;
        
        while(1){
            
            if( getbit(*gpio_reg_status, BIT_DDR_DATA_RDY) ){
                fwrite(ptr, sizeof(unsigned char), mem_size_in_Bytes, fb);
                ptr += n_col*n_bytes*4;
                row_cnt++;

                cnt++;
            } 

            if( row_cnt >= n_row ){
                break;
            }
        }

        gettimeofday(&end, NULL);
        printf("> FPGA readout used time: %10.6f s\n", time_diff(&start, &end));

        if( fb != NULL )
            fclose(fb);

        printf("> raw image data saved into: %s\n", filename);

        munmap(mem_ptr, n_row*n_col*16*n_bytes);
    } else {

        uint32_t mem_size_in_Bytes_X = n_col*n_row*16*n_bytes;   // the actual size
        uint32_t *mem_ptr = mmap(NULL, mem_size_in_Bytes_X, PROT_READ | PROT_WRITE, MAP_SHARED, fd, base_addr);
/*        memset(mem_ptr, 0, mem_size_in_Bytes);*/
        
        struct timeval start;
		struct timeval end;

        // 当fast_read_opt!=1时，回到旧版本的读出方式，将BIT_PS_DATA_SAVED设为1，PL端CCD驱动模块中的状态机就按照旧版本的方式运行
/*        setbit( *gpio_reg_ctrl, BIT_PS_DATA_SAVED );  // NOT USED @2025-03-4*/

        gettimeofday(&start, NULL);
        Trig_Readout( gpio_reg_ctrl );

        uint32_t rd_is_done = getbit( *gpio_reg_status, BIT_READOUT_DONE );
        
        printf("[CCD_READOUT] --> start\n");

        while( rd_is_done != 1 ){   // 判断CCD读出是否已经完成

            rd_is_done = getbit(*gpio_reg_status, BIT_READOUT_DONE);

            if( rd_is_done == 1 ){
                printf("[CCD_READOUT] --> finish!\n");
                // time(&t_end);
                // printf("[CCD_READOUT] --> used %ld seconds.\n", t_end - t_start);
                break;
            }

            // sleep(1);
            usleep(1000);
        }
        
        gettimeofday(&end, NULL);
        printf("FPGA readout used time: %g s\n", time_diff(&start, &end));

        // 开始从DDR中取出数据
        printf("[CCD_READOUT] --> start transferring data from DDR to disk ...\n");
        FILE *fb = NULL;
        fb = fopen(filename, "wb");
/*        gettimeofday(&start, NULL);*/
        fwrite((void *)mem_ptr, sizeof(unsigned char), mem_size_in_Bytes_X, fb);
        gettimeofday(&end, NULL);
        printf("writing array data into binary file used time: %g s\n", time_diff(&start, &end));

        if( fb != NULL )
            fclose(fb);

        munmap(mem_ptr, mem_size_in_Bytes_X);
    }

    close(fd);

    // DropCache();    //  清空缓存
    DropCaches(3);    //  清空缓存

    return 0;
}

/*
ccd_readout_fp的后缀fp表示写日志的文件描述符
@20240928: 在与ICS联调的过程中发现程序会卡死，因此先暂时切换回旧版本
@20240818: update the data storage startegy to reduce total readout time
*/
int ccd_readout_fp( uint32_t *gpio_reg_ctrl, 
                    uint32_t *gpio_reg_status, 
                    uint32_t *gpio_reg_pl_ps_ddr_cnt, 
                    uint32_t n_row,
                    uint32_t n_col,
                    int n_bytes,    // 2 for 16bits unsigned; 4 for 32bits float
                    int fast_read_opt,
                    char *filename,
                    FILE *fp ){

    uint32_t base_addr = 0x20000000;    // 512MByte 特殊地址值！！！！
    int fd = open("/dev/mem", O_RDWR | O_SYNC);

#if defined(_ENABLE_FASTER_READOUT_)
//  ===========================================================================================
//  @20240818: New and faster data storage strategy

    uint32_t mem_size_in_Bytes = n_col*16*n_bytes;   // the actual number of Bytes of a row
    uint32_t *mem_ptr = mmap(NULL, n_row*n_col*16*n_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, base_addr);
    uint32_t *ptr = mem_ptr;

    FILE *fb = NULL;
    fb = fopen(filename, "wba");   // append mode

    struct timeval start;
    struct timeval end;
    gettimeofday(&start, NULL);
    
    Trig_Readout( gpio_reg_ctrl );      // trigger readout

    uint32_t row_cnt = 0;
    while(1){
        
        if( getbit(*gpio_reg_status, BIT_DDR_DATA_RDY) ){
            fwrite(ptr, sizeof(unsigned char), mem_size_in_Bytes, fb);
            ptr += n_col*n_bytes*4;
            row_cnt++;
        }

        if( row_cnt >= n_row )
            break;
    }

    gettimeofday(&end, NULL);
    fprintf(stdout, "FPGA readout used time: %10.6f s\n", time_diff(&start, &end));

    if( fb != NULL )
        fclose(fb);

    munmap(mem_ptr, n_row*n_col*16*n_bytes);

#else
//  ===========================================================================================
//  **** Old and slow data storage strategy ****
    uint32_t mem_size_in_Bytes_X = n_col*n_row*16*n_bytes;   // the actual size
    uint32_t *mem_ptr = mmap(NULL, mem_size_in_Bytes_X, PROT_READ | PROT_WRITE, MAP_SHARED, fd, base_addr);

/*    setbit( *gpio_reg_ctrl, BIT_PS_DATA_SAVED );	// NOT USED @2025-03-4*/
    Trig_Readout( gpio_reg_ctrl );
    
    time_t t_start, t_end;
    time(&t_start);
    uint32_t rd_is_done = getbit( *gpio_reg_status, BIT_READOUT_DONE );
    printf("[CCD_READOUT] --> start\n");
    
    while( rd_is_done != 1 ){   // check whether readout is over
        rd_is_done = getbit(*gpio_reg_status, BIT_READOUT_DONE);
        if( rd_is_done == 1 ){
            printf("[CCD_READOUT] --> finish!\n");
            break;
        }
        usleep(1000);
    }

    write_log(fp, "start transferring data from DDR to disk ...");

    FILE *fb = NULL;
    fb = fopen(filename, "wb");
    printf("[CCD_READOUT] --> start transferring data from DDR to disk ...\n");
    fwrite((void *)mem_ptr, sizeof(unsigned char), mem_size_in_Bytes_X, fb);

    time(&t_end);
    printf("[CCD_READOUT] --> total time used %ld seconds.\n", t_end - t_start);

    write_log(fp, "finished transferring data.");

    if( fb != NULL )
        fclose(fb);
    
    munmap(mem_ptr, mem_size_in_Bytes_X);
#endif

    close(fd);
    DropCaches(3);    //  清空缓存

    return 0;
}

// ccd_exposure在ccd_readout的基础上增加了对快门的控制，其余功能一致
int ccd_exposure(   double t_exp, 
                    uint32_t *gpio_reg_ctrl, 
                    uint32_t *gpio_reg_status, 
                    uint32_t *gpio_reg_pl_ps_ddr_cnt, 
                    uint32_t n_row,
                    uint32_t n_col,
                    int n_bytes,
                    int fast_read_opt,
                    int is_dark,
                    char *filename,
                    char *log_shutter_fname,
                    char *log_camera_fname ){

    update_camera_status(2, log_camera_fname);

    if( is_dark == 1 ){
        // update_shutter_status(0, log_shutter_fname);
        uint32_t darktime_s = (uint32_t)(t_exp*1e6);
        fprintf(stdout, "[%s]--> taking a dark frame of %g seconds ...\n", __func__, t_exp);
        usleep(darktime_s);
        fprintf(stdout, "[%s]--> dark time %g seconds has passed ...\n", __func__, t_exp);
    } else {
        if( t_exp > 0 ){
            fprintf(stdout, "[%s]--> start exposuring for %g seconds ...\n", __func__, t_exp);
            update_shutter_status(1, log_shutter_fname);
            fprintf(stdout, "[%s]--> open shutter ...\n", __func__);
            bonn_shutter_exposure(  gpio_reg_ctrl, 
                                    gpio_reg_status,
                                    t_exp );
            fprintf(stdout, "[%s]--> close shutter ...\n", __func__);
            
            usleep(279000); // 279ms delay before readout
            update_shutter_status(0, log_shutter_fname);
        }
    }

    /* readout */
    update_camera_status(3, log_camera_fname);

    fprintf(stdout, "[%s]--> CCD readout starts ...\n", __func__);
    ccd_readout(    gpio_reg_ctrl, 
                    gpio_reg_status, 
                    gpio_reg_pl_ps_ddr_cnt,
                    n_row,
                    n_col,
                    n_bytes,
                    fast_read_opt,
                    filename );
    fprintf(stdout, "[%s]--> CCD readout ends ...\n", __func__);
    update_camera_status(0, log_camera_fname);

    return 0;
}

// // ccd_exposure在ccd_readout的基础上增加了对快门的控制，其余功能一致
// int ccd_exposure_with_abort(double t_exp, 
//                             uint32_t *gpio_reg_ctrl, 
//                             uint32_t *gpio_reg_status, 
//                             uint32_t *gpio_reg_pl_ps_ddr_cnt, 
//                             uint32_t n_row,
//                             uint32_t n_col,
//                             int n_bytes,
//                             int fast_read_opt,
//                             int is_dark,
//                             char *filename,
//                             char *log_shutter_fname,
//                             char *log_camera_fname ){

//     update_camera_status(2, log_camera_fname);

//     /* open shutter for a given time interval and then close it */
    
//     if( is_dark == 1 ){
//         // update_shutter_status(0, log_shutter_fname);
//         uint32_t darktime_s = (uint32_t)(t_exp*1e6);
//         fprintf(stdout, "[%s]--> taking a dark frame of %g seconds ...\n", __func__, t_exp);
//         usleep(darktime_s);
//         fprintf(stdout, "[%s]--> dark time %g seconds has passed ...\n", __func__, t_exp);
//     } else {
//         if( t_exp > 0 ){
//             fprintf(stdout, "[%s]--> start exposuring for %g seconds ...\n", __func__, t_exp);
//             update_shutter_status(1, log_shutter_fname);
//             fprintf(stdout, "[%s]--> open shutter ...\n", __func__);
//             bonn_shutter_exposure_with_abort( gpio_reg_ctrl, 
//                                               gpio_reg_status,
//                                               t_exp );
//             fprintf(stdout, "[%s]--> close shutter ...\n", __func__);
//             update_shutter_status(0, log_shutter_fname);
//         }
//     }

//     if( global_abort_exposure == 0 ){
//         /* readout */
//         update_camera_status(3, log_camera_fname);

//         fprintf(stdout, "[%s]--> CCD readout starts ...\n", __func__);
//         ccd_readout(    gpio_reg_ctrl, 
//                         gpio_reg_status, 
//                         gpio_reg_pl_ps_ddr_cnt,
//                         n_row,
//                         n_col,
//                         n_bytes,
//                         fast_read_opt,
//                         filename );
//         fprintf(stdout, "[%s]--> CCD readout ends ...\n", __func__);
//         // update_camera_status(0, log_camera_fname);        
//     }

//     update_camera_status(0, log_camera_fname);

//     return 0;
// }


// ccd_refresh发出信号驱动CCD读出，并可以根据传递的opt参数决定是否保存图像数据，以及如何保存
int ccd_refresh(    uint32_t *gpio_reg_ctrl, 
                    uint32_t *gpio_reg_status, 
                    uint32_t *gpio_reg_pl_ps_ddr_cnt, 
                    uint32_t n_row,
                    uint32_t n_col,
                    int n_bytes,    // 2 for 16bits unsigned; 4 for 32bits float
                    int fast_read_opt,
                    char *filename ){

        // 当fast_read_opt!=1时，回到旧版本的读出方式，将BIT_PS_DATA_SAVED设为1，PL端CCD驱动模块中的状态机就按照旧版本的方式运行
/*        setbit( *gpio_reg_ctrl, BIT_PS_DATA_SAVED );	// NOT USED @2025-03-4*/

        Trig_Readout( gpio_reg_ctrl );
        
        // time_t t_start, t_end;
        // time(&t_start);

        uint32_t rd_is_done = getbit( *gpio_reg_status, BIT_READOUT_DONE );
        
        printf("> [%s] --> start refreshing\n", __func__);

        // 判断CCD读出是否已经完成
        while( rd_is_done != 1 ){

            rd_is_done = getbit(*gpio_reg_status, BIT_READOUT_DONE);

            if( rd_is_done == 1 ){
                printf("> [%s] --> end refreshing\n", __func__);
                break;
            }

            // usleep(1000);   // sleep for 1ms
            usleep(10000);   // sleep for 10ms
        }

    return 0;
}

int DropCaches( int drop ){
    int ret = 0;
    
    int fd = open("/proc/sys/vm/drop_caches", O_RDWR);
    if( fd < 0 ){
        return -1;
    }
    
    char dropData[32] = {0};
    int dropSize = snprintf(dropData, sizeof(dropData), "%d", drop);
    
    ret = write(fd, dropData, dropSize);
    close(fd);
    
    return ret;
}

int DropCache(){
    
    int drop_ret;

    int opt;
    
    for( opt=1; opt<=3; opt++ ){
        drop_ret = DropCaches(3);
        
        if( drop_ret < 0 ){
            printf("# ERROR\n");
            printf("# src: %s func: %s L: %d, failed to drop caches (3) \n", __FILE__, __func__, __LINE__);
        } else {
            printf("# --> drop cache (%d) done.\n", opt);
        }
    }
    
    return 0;
}

int update_shutter_status( int status, char *fname ){

    if( strlen(fname) > 0 ){
        // printf("(update_shutter_status) --> status = %d\n", status);
        // printf("(update_shutter_status) : status written into : %s\n", fname);
        FILE *fp = fopen(fname, "w");
        fprintf(fp, "%d", status);
        fflush(fp);
        fclose(fp);
    }

    return 0;
}

int update_camera_status( int status, char *fname ){

    if( strlen(fname) > 0 ){
        // printf("(update_camera_status) --> status = %d\n", status);
        // printf("(update_camera_status) : status written into : %s\n", fname);
        FILE *fp = fopen(fname, "w");
        fprintf(fp, "%d", status);
        fflush(fp);
        fclose(fp);
    }

    return 0;
}

void gen_timestamp_op(char gmt_str[26]){
    struct timeval tv;    
    struct tm* st;
    gettimeofday(&tv, NULL);    
    
    st = gmtime(&tv.tv_sec);
    sprintf(gmt_str, "op%4d-%02d-%02dT%02d:%02d:%06.3f",st->tm_year+1900, st->tm_mon+1, st->tm_mday, st->tm_hour,st->tm_min,st->tm_sec+tv.tv_usec*1e-6);
}

void gen_timestamp_cl(char gmt_str[26]){
    struct timeval tv;    
    struct tm* st;
    gettimeofday(&tv, NULL);    
    
    st = gmtime(&tv.tv_sec);
    sprintf(gmt_str, "cl%4d-%02d-%02dT%02d:%02d:%06.3f",st->tm_year+1900, st->tm_mon+1, st->tm_mday, st->tm_hour,st->tm_min,st->tm_sec+tv.tv_usec*1e-6);
}


void get_gmtime(char gmt_str[32]){
    struct timeval tv;    
    struct tm* st;
    gettimeofday(&tv, NULL);    
    
    st = gmtime(&tv.tv_sec);
    sprintf(gmt_str, "%4d-%02d-%02d %02d:%02d:%06.3f",st->tm_year+1900, st->tm_mon+1, st->tm_mday, st->tm_hour,st->tm_min,st->tm_sec+tv.tv_usec*1e-6);
}

void write_log(FILE *fp, char *log_msg ){
    char gmt_str[32];
    get_gmtime(gmt_str);
    fprintf(fp, "%s: %s\n", gmt_str, log_msg);
}


int ccd_read_os_wave( uint32_t *gpio_reg_ctrl, 
                      uint32_t *gpio_reg_status, 
                      uint32_t *gpio_reg_pl_ps_ddr_cnt, 
                      uint32_t n_row,
                      uint32_t n_col,
                      uint32_t n_bytes,    // 2 for 16bits unsigned; 4 for 32bits float
                      uint32_t n_sample_per_reg,
                      uint32_t fast_read_opt,
                      char *filename ){

    uint32_t base_addr = 0x20000000;    // 512MByte 特殊地址值！！！！
    int fd = open("/dev/mem", O_RDWR | O_SYNC);

    uint32_t mem_size_in_Bytes_X = n_col*n_row*160*n_bytes;   // the actual size
    uint32_t *mem_ptr = mmap(NULL, mem_size_in_Bytes_X, PROT_READ | PROT_WRITE, MAP_SHARED, fd, base_addr);

    struct timeval start;
	struct timeval end;

    // 当fast_read_opt!=1时，回到旧版本的读出方式，将BIT_PS_DATA_SAVED设为1，PL端CCD驱动模块中的状态机就按照旧版本的方式运行
/*    setbit( *gpio_reg_ctrl, BIT_PS_DATA_SAVED );*/

    gettimeofday(&start, NULL);
    Trig_Readout( gpio_reg_ctrl );

    uint32_t rd_is_done = getbit( *gpio_reg_status, BIT_READOUT_DONE );
    
    printf("[CCD_READOUT] --> start\n");

    while( rd_is_done != 1 ){   // 判断CCD读出是否已经完成

        rd_is_done = getbit(*gpio_reg_status, BIT_READOUT_DONE);

        if( rd_is_done == 1 ){
            printf("[CCD_READOUT] --> finish!\n");
            break;
        }

        usleep(1000);
    }
    
    gettimeofday(&end, NULL);
    printf("FPGA readout used time: %g s\n", time_diff(&start, &end));

    // 开始从DDR中取出数据
    printf("[CCD_READOUT] --> start transferring data from DDR to disk ...\n");
    FILE *fb = NULL;
    fb = fopen(filename, "wb");
    fwrite((void *)mem_ptr, sizeof(unsigned char), mem_size_in_Bytes_X, fb);
    gettimeofday(&end, NULL);
    printf("writing array data into binary file used time: %g s\n", time_diff(&start, &end));

    if( fb != NULL )
        fclose(fb);

    munmap(mem_ptr, mem_size_in_Bytes_X);

    close(fd);

    DropCaches(3);    //  清空缓存

    return 0;
}
